﻿using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;

namespace DotNetCommon;

/// <summary>
/// 进程内异步锁 (由于lock关键字、Monitor无法对异步代码块加锁，所以封装了此类，它基于 <seealso cref="SemaphoreSlim"/> 实现)，使用示例：<br />
/// <list type="number">
/// <item> 锁代码块: 可设置超时
/// <code>
/// await AsyncLocker.LockAsync("123", async () =>
/// {
///     //somecode ...
/// },TimeSpan.FromSeconds(30));
/// </code>
/// </item>
/// <item> 尝试锁内运行
/// <code>
/// if (!await AsyncLocker.TryLockAsync("123", async () =>
/// {
///     //somecode...
/// }, TimeSpan.FromSeconds(30)))
/// {
///     return Result.NotOk("操作超时,请稍后重试!");
/// }
/// </code>
/// </item>
/// </list>
/// </summary>
public static class AsyncLocker
{
    private static readonly ConcurrentDictionary<string, SemaphoreSlim> _lockDic = new();

    /// <summary>
    /// 异步锁
    /// <code>
    /// await AsyncLocker.LockAsync("123", async () =>
    /// {
    ///     //somecode ...
    /// },TimeSpan.FromSeconds(30));
    /// </code>
    /// </summary>
    /// <param name="lockStr"></param>
    /// <param name="func"></param>
    /// <param name="timespan">超时时间</param>
    /// <returns></returns>
    /// <exception cref="TimeoutException">获取锁超时</exception>
    public static async Task LockAsync(string lockStr, Func<Task> func, TimeSpan? timespan = null)
    {
        var locker = _lockDic.GetOrAdd(lockStr, key => new SemaphoreSlim(1, 1));
        var flag = true;
        try
        {
            if (timespan != null)
            {
                flag = await locker.WaitAsync(timespan.Value);
                if (!flag) throw new TimeoutException($"异步锁超时({lockStr})!");
            }
            else await locker.WaitAsync();
            await func();
        }
        finally
        {
            if (flag) locker.Release();
        }
    }

    /// <summary>
    /// 尝试锁内运行, 成功在锁内运行后返回true, 进入锁失败后返回false
    /// <code>
    /// if (!await AsyncLocker.TryLockAsync("123", async () =>
    /// {
    ///     //somecode...
    /// }, TimeSpan.FromSeconds(30)))
    /// {
    ///     return Result.NotOk("操作超时,请稍后重试!");
    /// }
    /// </code>
    /// </summary>
    /// <param name="lockStr"></param>
    /// <param name="func"></param>
    /// <param name="timespan">超时时间</param>
    /// <returns></returns>
    /// <exception cref="TimeoutException">获取锁超时</exception>
    public static async Task<bool> TryLockAsync(string lockStr, Func<Task> func, TimeSpan timespan)
    {
        var locker = _lockDic.GetOrAdd(lockStr, key => new SemaphoreSlim(1, 1));
        var flag = true;
        try
        {
            flag = await locker.WaitAsync(timespan);
            if (!flag) return false;
            await func();
            return true;
        }
        finally
        {
            if (flag) locker.Release();
        }
    }

    /// <summary>
    /// 有返回值的锁
    /// <code>
    /// var person = await AsyncLocker.LockRetAsync("123", async () =>
    /// {
    ///     //somecode ...
    ///     return new Person();
    /// },TimeSpan.FromSeconds(30));
    /// </code>
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="lockStr"></param>
    /// <param name="func"></param>
    /// <param name="timespan">超时时间</param>
    /// <returns></returns>
    /// <exception cref="TimeoutException">获取锁超时</exception>
    public static async Task<T> LockRetAsync<T>(string lockStr, Func<Task<T>> func, TimeSpan? timespan = null)
    {
        var locker = _lockDic.GetOrAdd(lockStr, key => new SemaphoreSlim(1, 1));
        var flag = true;
        try
        {
            if (timespan != null)
            {
                flag = await locker.WaitAsync(timespan.Value);
                if (!flag) throw new TimeoutException($"异步锁超时({lockStr})!");
            }
            else await locker.WaitAsync();
            return await func();
        }
        finally
        {
            if (flag) locker.Release();
        }
    }
}
